﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Configuration.Provider;
using CoolCode.Linq;
using CoolCode.Entities;
using CoolCode.Data;

namespace CoolCode.Services
{
    public class MembershipService : ServiceBase, IRoleProvider, IMembershipService
    {
        #region Data Access Object

        private IRepository<aspnet_Roles> _RoleDao;
        public IRepository<aspnet_Roles> RoleDao
        {
            get
            {
                if (_RoleDao == null)
                {
                    _RoleDao = this.CreateRepository<aspnet_Roles>();
                }
                return _RoleDao;
            }
            set
            {
                _RoleDao = value;
            }
        }

        private IRepository<aspnet_Applications> _ApplicationDao;
        public IRepository<aspnet_Applications> ApplicationDao
        {
            get
            {
                if (_ApplicationDao == null)
                {
                    _ApplicationDao = this.CreateRepository<aspnet_Applications>();
                }
                return _ApplicationDao;
            }
            set
            {
                _ApplicationDao = value;
            }
        }

        private IRepository<aspnet_Users> _UserDao;
        public IRepository<aspnet_Users> UserDao
        {
            get
            {
                if (_UserDao == null)
                {
                    _UserDao = this.CreateRepository<aspnet_Users>();
                }
                return _UserDao;
            }
            set
            {
                _UserDao = value;
            }
        }

        private IRepository<aspnet_Membership> _MembershipDao;
        public IRepository<aspnet_Membership> MembershipDao
        {
            get
            {
                if (_MembershipDao == null)
                {
                    _MembershipDao = this.CreateRepository<aspnet_Membership>();
                }
                return _MembershipDao;
            }
            set
            {
                _MembershipDao = value;
            }
        }


        #endregion

        public MembershipService()
            : base()
        {
            this.LoadWith<aspnet_Membership>(c => c.aspnet_Users).EndLoad();
        }

        public MembershipService(string connectionString)
        { 
            this.CreateDataContextFrom(()=> new CoolCode.Data.Entity.XDataContext(new XProEntities(connectionString)));
            this.LoadWith<aspnet_Membership>(c => c.aspnet_Users).EndLoad();
        } 

        #region IRoleProvider Members

        private string _applicationName;

        public string ApplicationName
        {
            get
            {
                return _applicationName;
            }
            set
            {
                _applicationName = value;
            }
        }

        public void AddUsersToRoles(string[] usernames, string[] roleNames)
        {
            var lowerRoleNames = roleNames.Select(c => c.ToLower()).ToArray();
            var lowerUserNames = usernames.Select(c => c.ToLower()).ToArray();
            //var query = this.RoleDao.Where(c => lowerUserNames.Contains(c.RoleName));
            var query = this.RoleDao.In(c => c.RoleName, lowerRoleNames);
            var roles = query.ToList();
            //foreach (var username in lowerUserNames)
            //{
            //    var ur = this.UserInRoleDao.Where(c => c.aspnet_Users.LoweredUserName == username &&
            //      !roleNames.Contains(c.aspnet_Roles.RoleName));
            //    this.UserInRoleDao.Delete(ur);

            //} 

            //foreach (var role in roles)
            //{
            //    foreach (var username in lowerUserNames)
            //    {
            //        //var userInRoles = role.aspnet_UsersInRoles.Where(c => c.aspnet_User.LoweredUserName == username);
            //        //userInRoles.ToList().ForEach(ui => role.aspnet_UsersInRoles.Remove(ui));                 
            //        // aspnet_UsersInRole userInRole;
            //        aspnet_UsersInRoles userInRole = role.aspnet_UsersInRoles.FirstOrDefault(c => c.aspnet_Users.LoweredUserName == username);
            //        if (userInRole == null)
            //        {
            //            var user = this.UserDao.Where(c => c.LoweredUserName == username).FirstOrDefault();
            //            if (user != null)
            //            {
            //                userInRole = new aspnet_UsersInRoles
            //                {
            //                    RoleId = role.RoleId,
            //                    UserId = user.UserId
            //                };
            //                role.aspnet_UsersInRoles.Add(userInRole);
            //            }
            //        }
            //    }
            //}

            //var users = this.UserDao.Where(c => lowerUserNames.Contains(c.LoweredUserName)).ToList();
            var users = this.UserDao.In(c => c.LoweredUserName, lowerUserNames).ToList();

            //users.ForEach(c => roles.ForEach(r => c.aspnet_Roles.Remove(r)));
            users.ForEach(c =>
            {
                roles.ForEach(r =>
                {
                    if (!c.aspnet_Roles.Contains(r))
                        c.aspnet_Roles.Add(r);
                });
            });
            this.Submit();
        }

        public void CreateRole(string roleName)
        {
            if (RoleExists(roleName))
                throw new ProviderException("Role name already exists.");
            try
            {
                var app = this.ApplicationDao.Get(c => c.LoweredApplicationName == this.ApplicationName.ToLower());
                if (app == null)
                    app = this.ApplicationDao.FirstOrDefault();
                if (app == null)
                    throw new ProviderException("Application name not found.");
                this.RoleDao.Insert(new aspnet_Roles
                {
                    //ApplicationId = app.ApplicationId,
                    aspnet_Applications = app,
                    RoleId = Guid.NewGuid(),
                    RoleName = roleName,
                    LoweredRoleName = roleName.ToLower(),
                    Description = null
                });
                this.Submit();
            }
            catch
            {
                throw;
            }
        }

        public bool DeleteRole(string roleName, bool throwOnPopulatedRole)
        {
            if (!RoleExists(roleName))
            {
                throw new ProviderException("Role does not exist.");
            }
            if (GetUsersInRole(roleName).Length > 0)
            {
                if (throwOnPopulatedRole)
                    throw new ProviderException("Cannot delete a populated role.");
                return false;
            }

            try
            {
                this.RoleDao.Delete(c => c.LoweredRoleName == roleName.ToLower());
                this.Submit();
                return true;
            }
            catch
            {
                throw;
            }
        }

        public string[] FindUsersInRole(string roleName, string usernameToMatch)
        {
            var role = this.RoleDao.Get(c => c.LoweredRoleName == roleName.ToLower());
            if (role != null)
            {
                //return role.aspnet_UsersInRoles.Select(u => u.aspnet_Users.UserName).ToArray();
                return role.aspnet_Users.Where(u => u.UserName.Contains(usernameToMatch))
                    .Select(u => u.UserName)
                    .ToArray();
            }
            return new string[0];
        }

        public string[] GetAllRoles()
        {
            return this.RoleDao.Select(c => c.RoleName).ToArray();
        }

        public string[] GetRolesForUser(string username)
        {
            //var query = this.UserInRoleDao.Where(c => c.aspnet_Users.LoweredUserName == username.ToLower())
            //    .Select(c => c.aspnet_Roles.RoleName);
            //return query.ToArray();
            var user = this.UserDao.Get(c => c.LoweredUserName == username.ToLower());
            if (user != null)
            {
                return user.aspnet_Roles.Select(c => c.RoleName).ToArray();
            }
            return new string[0];
        }

        public string[] GetUsersInRole(string roleName)
        {
            var role = this.RoleDao.Get(c => c.LoweredRoleName == roleName.ToLower());
            if (role != null)
            {
                //return role.aspnet_UsersInRoles.Select(u => u.aspnet_Users.UserName).ToArray();
                return role.aspnet_Users.Select(u => u.UserName).ToArray();
            }
            return new string[0];
        }

        public bool IsUserInRole(string username, string roleName)
        {
            //return this.UserInRoleDao.Contains(c => c.aspnet_Users.LoweredUserName == username.ToLower() &&
            //    c.aspnet_Roles.LoweredRoleName == roleName.ToLower());
            return this.UserDao.Contains(c => c.LoweredUserName == username &&
                c.aspnet_Roles.Where(r => r.LoweredRoleName == roleName).Count() > 0);
        }

        public void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
        {
            //var query = this.RoleDao.Where(c => roleNames.Contains(c.RoleName));
            //var roles = query.ToList();
            //var lowerUserNames = usernames.Select(c => c.ToLower()).ToArray();
            //foreach (var role in roles)
            //{
            //    foreach (var username in lowerUserNames)
            //    {
            //        var user = role.aspnet_UsersInRoles.FirstOrDefault(c => c.aspnet_Users.LoweredUserName == username);
            //        if (user != null)
            //            role.aspnet_UsersInRoles.Remove(user);
            //        this.UserInRoleDao.Delete(user);
            //    }
            //}

            var lowerRoleNames = roleNames.Select(c => c.ToLower()).ToArray();
            var lowerUserNames = usernames.Select(c => c.ToLower()).ToArray();
            var query = this.RoleDao.Where(c => lowerUserNames.Contains(c.RoleName));
            var roles = query.ToList();

            var users = this.UserDao.Where(c => lowerUserNames.Contains(c.LoweredUserName)).ToList();

            users.ForEach(c =>
            {
                roles.ForEach(r =>
                {
                    if (c.aspnet_Roles.Contains(r))
                        c.aspnet_Roles.Remove(r);
                });
            });
            this.Submit();
        }

        public bool RoleExists(string roleName)
        {
            return this.RoleDao.Contains(c => c.LoweredRoleName == roleName.ToLower());
        }

        #endregion

        #region IMembershipService Members

        public aspnet_Membership NewUser(string username)
        {
            var app = GetApplicationByName(this.ApplicationName ?? "/");
            if (app == null)
            {
                app = new aspnet_Applications
                {
                    ApplicationId = Guid.NewGuid(),
                    ApplicationName = this.ApplicationName,
                    LoweredApplicationName = this.ApplicationName.ToLower(),
                    Description = null
                };
                SaveApplication(app);
            }

            aspnet_Membership user = new aspnet_Membership();
            user.aspnet_Applications = app;
            user.UserId = Guid.NewGuid();
            user.aspnet_Users = new aspnet_Users
            {
                UserId = user.UserId,
                UserName = username,
                LoweredUserName = username.ToLower(),
                LastActivityDate = DateTime.Now,
                //ApplicationId = app.ApplicationId
                aspnet_Applications =app
            };
            //app.aspnet_Membership.Add(user);
            
            return user;
        }

        public aspnet_Membership GetUserByKey(Guid key)
        {
            return this.MembershipDao.Get(c => c.UserId == key);
        }

        public aspnet_Membership GetUserByName(string userName)
        {
            return this.MembershipDao.Get(c => c.aspnet_Users.LoweredUserName == userName.ToLower());
        }

        public aspnet_Membership GetUserByEmail(string email)
        {
            return this.MembershipDao.Get(c => c.LoweredEmail == email.ToLower());
        }

        /// <summary>
        /// Validates the email.
        /// </summary>
        /// <param name="email">The email.</param>
        /// <param name="excludeKey">The exclude key.</param>
        /// <returns></returns>
        public bool ValidateEmail(string email, Guid excludeKey)
        {
            return !this.MembershipDao.Contains(c => c.LoweredEmail == email.ToLower() && c.UserId != excludeKey);
        }

        /// <summary>
        /// Validates the username.
        /// </summary>
        /// <param name="userName">Name of the user.</param>
        /// <param name="email">The email.</param>
        /// <param name="excludeKey">The exclude key.</param>
        /// <returns></returns>
        public bool ValidateUserName(string userName, Guid excludeKey)
        {
            return !this.UserDao.Contains(c => c.LoweredUserName == userName.ToLower() && c.UserId != excludeKey);
        }

        public void SaveUser(aspnet_Membership user)
        {
            this.MembershipDao.Insert(user);
            this.Submit();
        }

        public bool DeleteUser(string username, bool deleteAllRelatedData)
        {
            //var user = this.GetUserByName(username);
            //if (user != null)
            //{
            //    if (deleteAllRelatedData)
            //    {
            //        this.UserInRoleDao.Delete(c => c.UserId == user.UserId);
            //        this.UserDao.Delete(c => c.LoweredUserName == username);
            //    }
            //    this.MembershipDao.Delete(user);
            //    this.Submit();
            //    return true;
            //}
            var user = this.UserDao.Get(c => c.LoweredUserName == username);
            if (user != null)
            {
                if (deleteAllRelatedData)
                {
                    user.aspnet_Roles.Clear();
                    this.MembershipDao.Delete(c => c.aspnet_Users.LoweredUserName == username);
                }
                this.UserDao.Delete(user);
                this.Submit();
                return true;
            }
            return false;
        }

        public IPaginatedList<aspnet_Membership> PagingUserByName(string nameToSearch, PageParam p)
        {
            var query = this.MembershipDao.Where(c => c.aspnet_Users.LoweredUserName.Contains(nameToSearch.ToLower()))
             .OrderBy(c => c.aspnet_Users.UserName);
            return query.Paging(p);
        }

        public IPaginatedList<aspnet_Membership> PagingUserByEmail(string emailToSearch, PageParam p)
        {
            var query = this.MembershipDao.Where(c => c.LoweredEmail.Contains(emailToSearch.ToLower()))
             .OrderBy(c => c.aspnet_Users.UserName);
            return query.Paging(p);
        }

        public IPaginatedList<aspnet_Membership> PagingUser(IQueryBuilder<aspnet_Membership> queryBuilder, PageParam p)
        {
            var query = this.MembershipDao.Where(queryBuilder.Expression)
             .OrderBy(c => c.aspnet_Users.UserName);
            return query.Paging(p);
        }

        public IPaginatedList<aspnet_Membership> PagingUser(PageParam p)
        {
            var query = this.MembershipDao.OrderBy(c => c.aspnet_Users.UserName);
            return query.Paging(p);
        }

        public int GetNumberOfUsersOnline(DateTime lastActivityDate)
        {
            var query = this.UserDao.Where(c => c.LastActivityDate > lastActivityDate);
            return query.Count();
        }

        public aspnet_Applications GetApplicationByName(string name)
        {
            return this.ApplicationDao.Get(c => c.LoweredApplicationName == name.ToLower());
        }

        public void SaveApplication(aspnet_Applications app)
        {
            this.ApplicationDao.Insert(app);
            this.Submit();
        }

        #endregion
        
        #region ISearchService<aspnet_Membership> Members

        public IPaginatedList<aspnet_Membership> Paging(IQueryBuilder<aspnet_Membership> queryBuilder, PageParam p)
        {
            return PagingUser(queryBuilder, p);
        }

        public List<aspnet_Membership> List(IQueryBuilder<aspnet_Membership> queryBuilder)
        {
            return this.MembershipDao.Where(queryBuilder.Expression).ToList();
        }

        #endregion
 
    }
}
